#ifndef PYTABLESTABLEREADER_H_
#define PYTABLESTABLEREADER_H_

#include <string>
#include <hdf5.h>

#include "table.hpp"
#include "table_exception.hpp"

namespace pyt {
  template <class T> class table_reader: public table<T> {
  public:
    table_reader(const std::string& fname, const std::string& tblname); 
    virtual ~table_reader();
    hsize_t nrows() {
      return this->_nrows;
    }
    hsize_t nrows_read() {
      return this->_nrows_read;
    }

    herr_t read_row(T& row_out);
    herr_t append(T& row_out);
    std::string title();
    void reset();
  protected:
    herr_t fill(); 
    hsize_t _nrows_read;
  };
  
  template<class T> table_reader<T>::table_reader(const std::string& fname, const std::string& tblname): 
    table<T>(fname, tblname)
  {
    if (H5Fis_hdf5(fname.c_str()) <= 0) {
      std::string msg = "File `";
      msg += fname;
      msg += "' is not a valid HDF5 file.";
      throw table_exception(msg);
    } 

    this->_idfile = H5Fopen(this->_fname.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
    this->_idtbl = H5Dopen2(this->_idfile, this->_tblname.c_str(), H5P_DEFAULT); 
    this->_idfsp = H5Dget_space(this->_idtbl);
    this->_rank = H5Sget_simple_extent_ndims(this->_idfsp);
    
    H5Sget_simple_extent_dims(this->_idfsp, &this->_nrows, NULL);
    this->_cparms  = H5Dget_create_plist(this->_idtbl);
    
    if (H5Pget_layout(this->_cparms) != H5D_CHUNKED) {
      std::string msg = "Table `";
      msg += this->_tblname;
      msg += "' in file `";
      msg += this->_fname; 
      msg += "' is not in a chunked layout.";
      
      throw table_exception(msg);
  }
    
    this->_chunkrank = H5Pget_chunk(this->_cparms, 1, &this->_chunksize);
    this->_bufsize = (hsize_t) (this->_chunksize * parameters::BUF_TO_CHUNK_RATIO); 
    if (this->_bufsize > this->_nrows)
      this->_bufsize = (hsize_t) this->_nrows;

    this->_irow    = this->_bufsize + 1;
    this->_buf     = new T[this->_bufsize]; 
    _nrows_read     = 0;
  }
  
  template <class T> table_reader<T>::~table_reader() {
    H5Sclose(this->_idfsp);
    H5Pclose(this->_cparms);
    H5Dclose(this->_idtbl);
    H5Fclose(this->_idfile);

    delete[] this->_buf;
  }

  template <class T> herr_t table_reader<T>::append(T &row) {
    throw table_exception("Cannot append to table opened in read-only mode.");
  }

  template <class T> herr_t table_reader<T>::read_row(T &row) {
    if (this->_irow >= this->_bufsize) {
      this->_status |= fill();
      if (this->_status != 0)
	return this->_status;
    }
    
    row = this->_buf[this->_irow++];
    ++this->_nrows_read;
    return this->_status;
  }
  
  template <class T> herr_t table_reader<T>::fill() {
    hsize_t rows_left = this->_nrows - this->_offset;
    if (rows_left <= 0) {
      reset();
      return 1;
    }
    else if (rows_left < this->_bufsize)
      this->_bufsize = this->_nrows - this->_offset;
    
    hid_t idmsp = H5Screate_simple(this->_rank, &this->_bufsize, NULL);
    this->_status    |= H5Sselect_hyperslab(this->_idfsp, H5S_SELECT_SET, &this->_offset, 
					    NULL, &this->_bufsize, NULL);
  
    this->_status    |= H5Dread(this->_idtbl, T::h5_type(), idmsp, this->_idfsp, H5P_DEFAULT, this->_buf);
    
    this->_offset += this->_bufsize; 
    this->_irow = 0;
    
    H5Sclose(idmsp);
    return this->_status; 
  }
   

  template <class T> void table_reader<T>::reset() {
    this->_bufsize   = (hsize_t) (this->_chunksize * parameters::BUF_TO_CHUNK_RATIO);
    this->_offset    = 0; 
    this->_irow      = 0;
    _nrows_read = 0;
  }
}
#endif //PYTABLESTABLEREADER_H_
